#include "../../include/fileSystem/directory.h"
#include "../../include/fileSystem/fileSystem.h"
#include "../../include/fileSystem/fileDescriptor.h"
#include "../../include/fileSystem/inode.h"
#include "../../include/kc.h"

int rwByChunks(inodeTableEntry * fileInode, char * buf, int count,
		int filePos, char opType);

int
openImpl(char * pathName, short int mode) {
	inodeTableEntry * inode;

	//trato de crear el inodo del nuevo dir en base al path que me pasaron
	if ( (inode = createInode(pathName, NULL, mode)) == NULL) {
		return -1;
	}

	// si era 0, quiere decir que lo acabo de crear
	if (inode->inode.mode == 0) {
		//seteo el modo
		inode->inode.mode = FILE_BIT;

		//escribo el inodo
		writeInode(inode->inodeNum, &inode->inode);
	}
	else {
		//me fijo que no este abriendo un directorio
		if (inode->inode.mode & DIR_BIT) {
			putInode(inode);
			return -1;
		}
	}

	//busco un fileDescriptor del proceso libre para guardar el inodo
	int fd = setFileDes(inode, 0, mode);
	if (fd == -1) {
		putInode(inode);
		return -1;
	}

	//devuelvo el fd del inodo del archivo
	return fd;
}

void
closeImpl(int fd) {
	inodeTableEntry * fileInode;
	if ( (fileInode = getFdInode(fd)) == NULL)
		return;
	if (!(fileInode->inode.mode & FILE_BIT))
		return;

	freeFileDes(fd);
}

int
readImpl(int fd, char * buf, int count) {
	if (count <= 0)
		return 0;

	inodeTableEntry * fileInode;

	if ( (fileInode = getFdInode(fd)) == NULL)
		return -1;

	if (!(fileInode->inode.mode & FILE_BIT))
		return -1;

	//obtengo la posicion actual del archivo
	int filePos = getFilePos(fd);
	int fileEnd = fileInode->inode.fileSize;

	//si llegue al final del archivo, devuelvo 0
	if (filePos >= fileEnd)
		return 0;

	int cant;

	//leo de a chunks (cada chunk entra en un bloque). 0 es read
	if ( (cant = rwByChunks(fileInode, buf, count, filePos, 0)) < 0)
		return -1;

	//avanzo la posicion del archivo
	filePos += cant;
	updateFileDes(fd, filePos);

	return cant;
}

int
writeImpl(int fd, char * buf, int count) {
	if (count <= 0)
		return 0;

	inodeTableEntry * fileInode;

	if ( (fileInode = getFdInode(fd)) == NULL)
		return -1;

	//si no es un archivo o no lo abri con flags de escritura, salgo
	if (!(fileInode->inode.mode & FILE_BIT) || !(getFileFlags(fd) & O_WRITE))
		return -1;

	//obtengo la posicion actual del archivo
	int filePos = getFilePos(fd);

	int cant;
	//escribo de a chunks, para que entren en los bloques. 1 es write
	if ( (cant = rwByChunks(fileInode, buf, count, filePos, 1)) < 0)
		return -1;

	//avanzo la posicion del archivo
	filePos += cant;
	//modifico el tamaño del archivo
	fileInode->inode.fileSize = filePos;
	updateFileDes(fd, filePos);

	writeInode(fileInode->inodeNum, &fileInode->inode);

	return cant;
}

int
unlinkImpl(const char * pathParam) {
	return doUnlink(pathParam, FILE_UNLINK);
}

int
renameImpl(const char * oldPathParam, const char * newPathParam) {
	char oldName[FILE_NAME_LEN+1], oldPath[MAX_PATH_LEN];
	char newName[FILE_NAME_LEN+1], newPath[MAX_PATH_LEN];

	strcpy(oldPath, oldPathParam);
	strcpy(newPath, newPathParam);

	//obtengo el nombre del archivo o directorio original
	getNameFromPath(oldName, oldPath);
	//obtengo el nuevo nombre
	getNameFromPath(newName, newPath);

	inodeTableEntry * oldDirInode;
	inodeTableEntry * newDirInode;

	//obtengo el inodo del directorio donde se encuentra el nombre a cambiar
	if ( (oldDirInode = getInode(oldPath)) == NULL ) {
		return -1;
	}
	//obtengo el inodo del directorio donde va a ir el nuevo nombre
	if ( (newDirInode = getInode(newPath)) == NULL ) {
		putInode(oldDirInode);
		return -1;
	}

	int errorFlag = -1;
	//el directorio del nombre viejo y el del nombre nuevo tienen que coincidir
	if (oldDirInode->inodeNum == newDirInode->inodeNum) {
		//trato de renombrar el archivo o directorio
		errorFlag = renameDirEntry(oldDirInode, oldName, newName);
	}

	putInode(oldDirInode);
	putInode(newDirInode);

	return errorFlag;
}

int
getFileSizeImpl(const char * pathParam) {
	char path[MAX_PATH_LEN];

	strcpy(path, pathParam);

	inodeTableEntry * inode;

	//obtengo el inodo del archivo del que quiero obtener el tamaño
	if ( (inode = getInode(path)) == NULL ) {
		return -1;
	}

	int size = inode->inode.fileSize;

	putInode(inode);

	return size;
}

int
rwByChunks(inodeTableEntry * fileInode, char * buf, int count,
		int filePos, char opType) {
	int cant, blockIdx, blockPos, total = 0;

	int fileEnd = fileInode->inode.fileSize;
	//si es read, veo que no me vaya fuera del limite del archivo
	if (opType == 0) {
		count = (count <= (fileEnd - filePos))? count : (fileEnd - filePos);
	}

	while (count > 0) {
		//calculo el bloque que debo elegir
		blockIdx = filePos / BLOCK_SIZE;
		//calculo la posicion sobre el bloque
		blockPos = filePos % BLOCK_SIZE;

		//escribo de a tamaño de bloque como mucho
		cant = (count <= (BLOCK_SIZE - blockPos))? count : (BLOCK_SIZE - blockPos);

		//actualizo count
		count -= cant;

		int blockNum = -1;

		if (blockIdx < BLOCKS_PER_INODE - 1) {
			//si el bloque estaba en 0, lo alloco
			if (fileInode->inode.blockNum[blockIdx] == 0) {
				fileInode->inode.blockNum[blockIdx] = allocBlock();
			}
			blockNum = fileInode->inode.blockNum[blockIdx];
		}
		else {
			int * indirBlock;
			if (fileInode->inode.blockNum[BLOCKS_PER_INODE-1] == 0) {
				fileInode->inode.blockNum[BLOCKS_PER_INODE-1] = allocBlock();
				indirBlock = (int*)getBlock(fileInode->inode.blockNum[BLOCKS_PER_INODE-1]);

				if (indirBlock == NULL) {
					return -1;
				}

				indirBlock[0] = 0;
			}
			else {
				indirBlock = (int*)getBlock(fileInode->inode.blockNum[BLOCKS_PER_INODE-1]);
			}

			blockIdx -= (BLOCKS_PER_INODE - 1);

			if (blockIdx >= (BLOCK_SIZE / sizeof(int))) {
				putBlock(fileInode->inode.blockNum[BLOCKS_PER_INODE-1], (char*)indirBlock);
				return -1;
			}

			if (indirBlock[blockIdx] == 0) {
				indirBlock[blockIdx] = allocBlock();
				if (blockIdx != (BLOCK_SIZE / sizeof(int)) - 1)
					indirBlock[blockIdx+1] = 0;
			}

			blockNum = indirBlock[blockIdx];

			putBlock(fileInode->inode.blockNum[BLOCKS_PER_INODE-1], (char*)indirBlock);
		}

		//busco el bloque del archivo
		char * memDir = getBlock(blockNum);

		if (memDir == NULL) {
			return -1;
		}

		//veo si es read
		if (opType == 0) {
			//copio del bloque a buf
			memcpy(buf, memDir + blockPos, cant);
		}
		else {
			//copio de buf al bloque
			memcpy(memDir + blockPos, buf, cant);
			//actualizo el bloque
			writeBlock(blockNum, memDir);
		}

		//devuelvo el bloque
		putBlock(blockNum, memDir);

		//actualizo la posicion del archivo y el buffer
		filePos += cant;
		buf += cant;
		total += cant;
	}

	return total;
}
